/**
 * Copyright (c) 2015-2017, Henry Yang 杨勇 (gismail@foxmail.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lambkit.module.upms.client.auth;

import com.jfinal.core.Controller;
import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import com.lambkit.Lambkit;
import com.lambkit.common.service.LambkitService;
import com.lambkit.common.service.Service;
import com.lambkit.common.util.DateTimeUtils;
import com.lambkit.common.util.RequestUtils;
import com.lambkit.component.shiro.session.ShiroSession;
import com.lambkit.db.sql.column.Example;
import com.lambkit.module.upms.UpmsCache;
import com.lambkit.module.upms.UpmsConfig;
import com.lambkit.module.upms.UpmsManager;
import com.lambkit.module.upms.api.*;
import com.lambkit.module.upms.client.UpmsLogService;
import com.lambkit.module.upms.common.UpmsResult;
import com.lambkit.module.upms.common.UpmsResultConstant;
import com.lambkit.module.upms.common.Validator;
import com.lambkit.module.upms.encrypt.RSAKeyRender;
import com.lambkit.module.upms.record.*;
import com.lambkit.module.upms.shiro.UpmsUsernamePasswordToken;
import com.lambkit.module.upms.shiro.ShiroCacheSessionDao;
import com.lambkit.module.upms.sql.UpmsSystemSqlBuilder;
import com.lambkit.plugin.auth.AuthService;
import com.lambkit.plugin.auth.AuthUser;
import com.lambkit.common.util.StringUtils;
import com.lambkit.web.LambkitResult;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;

import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 重构，按照postProcess方式
 */
@Service
@LambkitService
public class UpmsAuthServiceImpl implements AuthService {

	private static final int AccountLockerNum = 6;


	private UpmsApiService getUpmsApiService() {
		return UpmsManager.me().getUpmsApiService();
	}

	public AuthUser upmsUserToAuthUser(HttpServletRequest request, String token, UpmsUser upmsUser) {
		AuthUser authUser = new AuthUser();
		authUser.setToken(token);
		authUser.setUserId(upmsUser.getUserId());
		authUser.setUsername(upmsUser.getUsername());
		authUser.setRealname(upmsUser.getRealname());
		authUser.setLoginTime(DateTimeUtils.getCurrentTimeLong());
		Subject subject = SecurityUtils.getSubject();
		Session session = subject.getSession();
		authUser.setExpireTime(authUser.getLoginTime() + session.getTimeout());
		authUser.setIpaddr(RequestUtils.getIpAddress(request));
		authUser.setUserBean(upmsUser);

		boolean superRole = false;
		// 当前用户所有角色
		List<UpmsRole> upmsRoles = getUpmsApiService().selectUpmsRoleByUpmsUserId(upmsUser.getUserId());
		Set<String> roles = new HashSet<>();
		for (UpmsRole upmsRole : upmsRoles) {
			if (StringUtils.isNotBlank(upmsRole.getName())) {
				roles.add(upmsRole.getName());
				if("super".equals(upmsRole.getName())) {
					superRole = true;
				}
			}
		}
		authUser.setRoles(roles);

		if(superRole) {
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			//超级管理员 始终拥有所有权限
			Set<String> permissions = new HashSet<>();
			permissions.add("*");
			authUser.setPermissions(permissions);
			authUser.setType("super");
		} else {
			// 当前用户所有权限
			List<UpmsPermission> upmsPermissions = getUpmsApiService().selectUpmsPermissionByUpmsUserId(upmsUser.getUserId());
			Set<String> permissions = new HashSet<>();
			for (UpmsPermission upmsPermission : upmsPermissions) {
				if (StringUtils.isNotBlank(upmsPermission.getPermissionValue())) {
					//permissions.add(upmsPermission.getPermissionValue());
					permissions.add(upmsPermission.getUri());
				}
			}
			authUser.setPermissions(permissions);
			authUser.setType("normal");
		}
		return authUser;
	}

	public AuthUser authenticate(String appid, String sessionId) {
		// TODO Auto-generated method stub
		if (StringUtils.isBlank(appid) || StringUtils.isBlank(sessionId)) {
			if(Lambkit.isDevMode()) {
				System.out.println("无效访问appid or sessionid is null");
			}
			return null;
		}
		// 判断请求认证系统是否注册
		Example upmsSystemExample = UpmsSystemSqlBuilder.create()
				.andNameEqualTo(appid).example();
		Long count = getUpmsApiService().selectUpmsSystemCount(upmsSystemExample);
		if (count==null || 0 == count) {
			if(Lambkit.isDevMode()) {
				System.out.println(String.format("未注册的系统:%s", appid));
			}
			return null;
		}
		// 判断是否已登录
		return getCache().getAuthUser(sessionId);
	}

	public AuthUser authenticate(String appid, String username, String sessionId) {
		AuthUser authUser = authenticate(appid, sessionId);
		return authUser;
	}

	public AuthUser authenticate(String sessionId) {
		// TODO Auto-generated method stub
		if (StringUtils.isBlank(sessionId)) {
			if(Lambkit.isDevMode()) {
				System.out.println("无效访问sessionid is null");
			}
			return null;
		}
		// 判断是否已登录
		return getCache().getAuthUser(sessionId);
	}

	@Override
	public boolean user() {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		Session session = subject.getSession();
		String serverSessionId = session.getId().toString();
		String code = getCache().getSession(serverSessionId);
		return StrKit.notBlank(code) ? true : false;
	}

	@Override
	public AuthUser getUser() {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		return getCache().getAuthUser(subject.getSession().getId().toString());
//		Object obj = subject.getPrincipal();
//		if (obj == null) {
//			return null;
//		} else {
//
//		}
	}

	public LambkitResult login(Controller controller) {
		Subject subject = SecurityUtils.getSubject();
		Session session = subject.getSession();
		String sessionId = session.getId().toString();
		String username = controller.getRequest().getParameter("username");

		if(getCache().getLoginError(sessionId) > AccountLockerNum) {
			LambkitResult result = new UpmsResult(UpmsResultConstant.INVALID_ACCOUNT, "设备已锁定！");
			// 记录用户登录log
			Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
			return result;
		}
		if(getCache().getLoginError(username) > AccountLockerNum) {
			LambkitResult result = new UpmsResult(UpmsResultConstant.INVALID_ACCOUNT, "帐号已锁定！");
			// 记录用户登录log
			Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
			return result;
		}
		String password = controller.getRequest().getParameter("password");
		//String rememberMe = controller.getRequest().getParameter("rememberMe");
		if (StringUtils.isBlank(username)) {
			LambkitResult result = new UpmsResult(UpmsResultConstant.EMPTY_USERNAME, "帐号不能为空！");
			// 记录用户登录log
			Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
			return result;
		}

		//int accountType = 0;
		int loginType = 0;

		// 查询用户信息
		UpmsUser upmsUser = null;
		if(Validator.isMobile(username)) {
			//accountType = 1;
			loginType = 1;
			upmsUser = getUpmsApiService().selectUpmsUserByPhone(username);
		} else if(Validator.isEmail(username)) {
			//accountType = 2;
			upmsUser = getUpmsApiService().selectUpmsUserByEmail(username);
		} else {
			// 查询用户信息
			upmsUser = getUpmsApiService().selectUpmsUserByLoginName(username);
		}

		String typeStr = controller.getRequest().getParameter("type");
		if("account".equalsIgnoreCase(typeStr)) {
			loginType = 0;
		} else if("sms".equalsIgnoreCase(typeStr)) {
			loginType = 1;
		}

		if(loginType==1) {
			if(StringUtils.isBlank(password)) {
				LambkitResult result = new UpmsResult(UpmsResultConstant.EMPTY_CAPTCHA, "验证码不能为空！");
				// 记录用户登录log
				Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
				return result;
			}
		} else {
			if (StringUtils.isBlank(password)) {
				LambkitResult result = new UpmsResult(UpmsResultConstant.EMPTY_PASSWORD, "密码不能为空！");
				// 记录用户登录log
				Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
				return result;
			}
			password = RSAKeyRender.getPassword(controller, password);
			if (StringUtils.isBlank(password)) {
				LambkitResult result = new UpmsResult(UpmsResultConstant.EMPTY_PASSWORD, "帐号或密码错误！错误6次后锁定!");
				// 记录用户登录log
				Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
				return result;
			}
			String captcha = controller.getRequest().getParameter("captcha");
			if (StringUtils.isBlank(captcha)) {
				LambkitResult result = new UpmsResult(UpmsResultConstant.EMPTY_CAPTCHA, "验证码不能为空！");
				// 记录用户登录log
				Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
				return result;
			}
			if (!controller.validateCaptcha("captcha")) {
				// 判断请求认证系统是否注册
				Example upmsSystemExample = UpmsSystemSqlBuilder.create()
						.andNameEqualTo(captcha).example();
				Long count = getUpmsApiService().selectUpmsSystemCount(upmsSystemExample);
				if (count==null || 0 == count) {
					LambkitResult result = new UpmsResult(UpmsResultConstant.INVALID_CAPTCHA, "验证码不正确！");
					// 记录用户登录log
					Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
					return result;
				}
			}
		}

		if (null == upmsUser) {
			Integer error = getCache().addLoginError(sessionId);
			LambkitResult result = new UpmsResult(UpmsResultConstant.INVALID_USERNAME, "帐号或密码错误！错误6次后锁定!");//帐号不存在！错误" + error + "次，错误6次后锁定
			// 记录用户登录log
			Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
			return result;
		}

		//登录操作
		UpmsUsernamePasswordToken usernamePasswordToken = new UpmsUsernamePasswordToken(username, password, loginType);
		UpmsConfig config = Lambkit.config(UpmsConfig.class);
		if(config.isRememberMe()) {
			usernamePasswordToken.setRememberMe(true);
		}
		try {
			subject.login(usernamePasswordToken);
		} catch (UnknownAccountException e) {
			Integer error = getCache().addLoginError(sessionId);
			LambkitResult result = new UpmsResult(UpmsResultConstant.INVALID_USERNAME, "帐号或密码错误！错误6次后锁定!");//错误" + error + "次，
			// 记录用户登录log
			Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
			return result;
		} catch (IncorrectCredentialsException e) {
			Integer error = getCache().addLoginError(username);
			if(loginType==1) {
				LambkitResult result = new UpmsResult(UpmsResultConstant.INVALID_CAPTCHA, "验证码错误！错误6次后锁定!");//错误" + error + "次，
				// 记录用户登录log
				Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
				return result;
			} else {
				LambkitResult result = new UpmsResult(UpmsResultConstant.INVALID_PASSWORD, "帐号或密码错误！错误6次后锁定!");//错误" + error + "次，
				// 记录用户登录log
				Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
				return result;
			}

		} catch (LockedAccountException e) {
			LambkitResult result = new UpmsResult(UpmsResultConstant.INVALID_ACCOUNT, "帐号已锁定！");
			// 记录用户登录log
			Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
			return result;
		}

		// 更新session状态
		ShiroCacheSessionDao upmsSessionDao = Lambkit.get(ShiroCacheSessionDao.class);
		upmsSessionDao.updateStatus(sessionId, username, ShiroSession.OnlineStatus.on_line);
		getCache().saveSession(sessionId, upmsUser.getUsername(), (int) session.getTimeout() / 1000);
		//loginSuccess(request, username);
		String type = controller.getRequest().getParameter("type");
		if(StrKit.notBlank(type) && "info".equals(type) && getUpmsApiService()!=null) {
			Kv data = Kv.by("user", upmsUser);
			// 用户拥有组织
			List<UpmsUserOrganization> upmsUserOrganizations = getUpmsApiService().selectUpmsUserOrganizationByUpmsUserId(upmsUser.getUserId());
			data.set("userOrganizations", upmsUserOrganizations);
			// 用户拥有角色
			List<UpmsUserRole> upmsUserRoles = getUpmsApiService().selectUpmsUserRoleByUpmsUserIdByCache(upmsUser.getUserId());
			data.set("userRoles", upmsUserRoles);
			LambkitResult result = new UpmsResult(UpmsResultConstant.SUCCESS, sessionId, data);
			// 记录用户登录log
			Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
			loginSuccess(controller.getRequest(), upmsUser);
			return result;
		} else {
			LambkitResult result = new UpmsResult(UpmsResultConstant.SUCCESS, sessionId, sessionId);
			// 记录用户登录log
			Lambkit.get(UpmsLogService.class).addLoginLog(username, result, controller.getRequest());
			loginSuccess(controller.getRequest(), upmsUser);
			return result;
		}
	}

	/**
	 * client无密登录
	 * @param request
	 * @param username
	 * @return
	 */
	public String login(HttpServletRequest request, String username) {
		Subject subject = SecurityUtils.getSubject();
		Session session = subject.getSession();
		String sessionId = session.getId().toString();
		UpmsUsernamePasswordToken talkUserNameToken = new UpmsUsernamePasswordToken(username,"", 99);
		try {
			subject.login(talkUserNameToken);
		} catch (UnknownAccountException e) {
			e.printStackTrace();
		} catch (IncorrectCredentialsException e) {
			e.printStackTrace();
		} catch (LockedAccountException e) {
			e.printStackTrace();
		}
		// 更新session状态
		ShiroCacheSessionDao upmsSessionDao = Lambkit.get(ShiroCacheSessionDao.class);
		upmsSessionDao.updateStatus(sessionId, username, ShiroSession.OnlineStatus.on_line);
		getCache().saveSession(sessionId, username, (int) session.getTimeout() / 1000);
		loginSuccess(request, getUpmsUser(username));
		// 记录用户登录log
		LambkitResult result = new UpmsResult(UpmsResultConstant.SUCCESS, sessionId, sessionId);
		Lambkit.get(UpmsLogService.class).addLoginLog(username, result, request);
		return sessionId;
	}

	public void loginSuccess(HttpServletRequest request, UpmsUser upmsUser) {
		AuthUser user = upmsUserToAuthUser(request, getSessionId(request), upmsUser);
		getCache().login(user, getSessionId(request));
	}

	public UpmsUser getUpmsUser(String username) {
		// 查询用户信息
		UpmsUser upmsUser = null;
		if(getUpmsApiService()!=null) {
			if(Validator.isMobile(username)) {
				upmsUser = getUpmsApiService().selectUpmsUserByPhone(username);
			} else if(Validator.isEmail(username)) {
				upmsUser = getUpmsApiService().selectUpmsUserByEmail(username);
			} else {
				// 查询用户信息
				upmsUser = getUpmsApiService().selectUpmsUserByLoginName(username);
			}
		}
		return upmsUser;
	}

	public LambkitResult logout(HttpServletRequest request) {
		// 登录信息
		Subject subject = SecurityUtils.getSubject();
		String username = (String) subject.getPrincipal();
		logoutSuccess(request);
		// shiro退出登录
		SecurityUtils.getSubject().logout();
		// 跳回原地址
		String redirectUrl = request.getHeader("Referer");
		LambkitResult result = new UpmsResult(UpmsResultConstant.SUCCESS, redirectUrl);
		// 记录用户log
		Lambkit.get(UpmsLogService.class).addLogoutLog(username, result, request);
		return result;
	}

	public LambkitResult logout(Controller controller) {
		// 登录信息
		Subject subject = SecurityUtils.getSubject();
		String username = (String) subject.getPrincipal();
		logoutSuccess(controller);
		// shiro退出登录
		SecurityUtils.getSubject().logout();
		// 跳回原地址
		String redirectUrl = controller.getHeader("Referer");
		LambkitResult result = new UpmsResult(UpmsResultConstant.SUCCESS, redirectUrl);
		// 记录用户log
		Lambkit.get(UpmsLogService.class).addLogoutLog(username, result, controller.getRequest());
		return result;
	}

	public void logoutSuccess(HttpServletRequest request) {
		getCache().logout(getSessionId(request));
		request.getSession().invalidate();
	}

	public void logoutSuccess(Controller controller) {
		getCache().logout(getSessionId(controller.getRequest()));
		controller.getSession().invalidate();
	}

	@Override
	public String getPasswordSecurity(String username, String password) {
		// TODO Auto-generated method stub
		UpmsUser user = getUpmsApiService().selectUpmsUserByUsername(username);
		if(user!=null && StrKit.notBlank(password)) {
			return UpmsManager.me().encrypt(password + user.getSalt());
		}
		return null;
	}

	@Override
	public AuthUser findByUsernameForLogin(String userName) {
		// TODO Auto-generated method stub
		//UpmsUser user = getUpmsApiService().selectUpmsUserByUsername(userName);
		String sessionId = getCache().getSessionId(userName);
		return getCache().getAuthUser(sessionId);
	}

	@Override
	public Boolean isGuestRule(String sessionId) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<?> getRoles(Object userid) {
		// TODO Auto-generated method stub
		if(userid!=null) {
			return getUpmsApiService().selectUpmsRoleByUpmsUserId(Long.valueOf(userid.toString()));
		}
		return null;
	}

	@Override
	public List<?> getRules(Object userid) {
		// TODO Auto-generated method stub
		if(userid!=null) {
			return getUpmsApiService().selectUpmsPermissionByUpmsUserId(Long.valueOf(userid.toString()));
		}
		return null;
	}

	@Override
	public UpmsCache getCache() {
		return UpmsManager.me().getCache();
	}

	@Override
	public String getSessionId(HttpServletRequest request) {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		return subject.getSession().getId().toString();
	}

	@Override
	public String getSessionCode(String sessionId) {
		return null;
	}

	@Override
	public Boolean hasRole(String roleid) {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		if(subject!=null) {
			subject.hasRole(roleid);
		}
		return false;
	}

	@Override
	public Boolean hasRule(int ruleid) {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		if(subject!=null) {
			subject.isPermitted(String.valueOf(ruleid));
		}
		return false;
	}

	@Override
	public boolean authenticated() {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		return subject.isAuthenticated();
	}

	@Override
	public boolean notAuthenticated() {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		return subject.isRemembered();
	}

	@Override
	public boolean guest() {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		return subject.hasRole("guest");
	}

	@Override
	public Boolean hasRole(int roleid) {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		UpmsRole role = getUpmsApiService().selectUpmsRoleByRoleId(Long.valueOf(roleid));
		if(role==null) {
			return false;
		}
		return subject.hasRole(role.getName());
	}

	@Override
	public Boolean lacksRole(String roleName) {
		// TODO Auto-generated method stub
		return getUpmsApiService().lacksRole(roleName);
	}

	@Override
	public boolean hasAnyRoles(String roleNames) {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		boolean[] flag = subject.hasRoles(StringUtils.strToList(roleNames, ","));
		for (boolean f: flag) {
			if(f) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean hasAllRoles(String roleNames) {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		return subject.hasAllRoles(StringUtils.strToList(roleNames, ","));
	}

	@Override
	public boolean hasRule(String permission) {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		return subject.isPermitted(permission);
	}

	@Override
	public boolean lacksRule(String permission) {
		// TODO Auto-generated method stub
		return getUpmsApiService().lacksRule(permission);
	}

	public boolean hasAnyRules(String permissions) {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		boolean[] flag = subject.isPermitted(StringUtils.strToList(permissions, ","));
		for (boolean f: flag) {
			if(f) {
				return true;
			}
		}
		return false;
	}

	public boolean hasAllRules(String permissions) {
		// TODO Auto-generated method stub
		Subject subject = SecurityUtils.getSubject();
		return subject.isPermittedAll(StringUtils.strToList(permissions, ","));
	}
}
